#include "filesocket.h"

#include <QDataStream>
#include <QByteArray>
#include <QMap>
#include <QVariant>
#include <QString>
#include <QCryptographicHash>
#include "widget.h"

//默认的存储文件的目录
//const QString g_root_dir = "";

FileSocket::FileSocket(qint32 param_id,qintptr param_socketDescriptor,QObject *parent) :
    QObject(parent),
    m_id(param_id),
    m_socket(this),
    m_fileupload_state(0),
    m_local_file(this)
{
    this->m_msgBytes.resize(0);
    this->paramInit();
    this->m_socket.setSocketDescriptor(param_socketDescriptor);

    connect(&(this->m_socket),SIGNAL(error(QAbstractSocket::SocketError)),this,SLOT(socketError(QAbstractSocket::SocketError)));
    //绑定readyRead信号，进行数据读取
    connect(&(this->m_socket),SIGNAL(readyRead()),this,SLOT(fileRead()));
}

FileSocket::~FileSocket()
{

}

void FileSocket::socketError(QAbstractSocket::SocketError param_error)
{
    qDebug()<<"socket error("<<param_error<<"): "<<this->m_socket.errorString()<<endl;
    this->m_fileupload_state=0;
    this->socketDisconnect();
}

void FileSocket::socketDisconnect()
{
    this->m_socket.disconnectFromHost();
    emit socketClose(this->m_id);
    this->deleteLater();
}

//读取文件
void FileSocket::fileRead()
{
    this->m_msgBytes.resize(0);
    this->m_msgBytes=this->m_socket.readAll();

    //第一次接收客户端数据，包括请求类型，以及文件名称
    if(this->m_fileupload_state==0)
    {
        QDataStream file_in(&this->m_msgBytes,QIODevice::ReadOnly);
        QMap<QString,QVariant> param_qst_file;
        file_in>>this->m_req_id>>param_qst_file;

        QByteArray paramBytes;
        bool reply=true;
        QMap<QString,QVariant> param_rst_file;

        //进行文件处理，根据请求类型进行一系列处理
        this->fileHandle(this->m_req_id,param_qst_file,param_rst_file,reply);
        //服务器接收消息后，对客户端进行回传
        if(reply==true)
        {
            QDataStream msg_out(&paramBytes,QIODevice::WriteOnly);
            msg_out<<qint64(this->m_req_id)<<param_rst_file;
            this->m_socket.write(paramBytes);
            this->m_socket.waitForBytesWritten();
        }
    }

    //开始正式接收文件
    else
    {
        //文件上传的时候，开始数据写入
        this->m_file_totalBytes -= this->m_msgBytes.size();
        if(this->m_status!=OK)
        {
            return;
        }

        qint64 wrtieBytes=this->m_local_file.write(this->m_msgBytes);
        if(wrtieBytes==-1)
        {
            qDebug()<<"file write error"<<endl;
            this->m_status=FILE_WRITE_ERROR;
            return;
        }

        //数据接收完毕后向客户端回传信息
        if(this->m_file_totalBytes==0)
        {
            this->m_local_file.close();
            QByteArray paramBytes;
            QMap<QString,QVariant> param_rst_file;
            param_rst_file[QString("reply_status")]=QVariant(this->m_status);

            QDataStream msg_out(&paramBytes,QIODevice::WriteOnly);
            msg_out<<qint64(FILE_UPLOAD_TAIL)<<param_rst_file;
            this->m_socket.write(paramBytes);
            this->m_socket.waitForBytesWritten();
            this->m_fileupload_state=0;
        }
    }
}

void FileSocket::fileHandle(qint64 param_request_id,QMap<QString,QVariant>& param_qst_file,QMap<QString,QVariant>& param_rst_file,bool& reply)
{
    switch (param_request_id)
    {
    //如果客户端请求类型为上传头，解析客户端发送端的信息，回传接收状态、偏移量以及MD5值
    case FILE_UPLOAD_HEADER:{
        this->paramInit();
        //获取客户端上传文件的名称
        QString file_prjpath=param_qst_file[QString("file_prjpath")].toString();

        qint64 pos=0;
        QString md5_value;
        qint64 status=this->fileUploadHeader(file_prjpath,pos,md5_value);
        if(status==OK)
        {
            this->m_fileupload_state=0;
        }

        param_rst_file[QString("reply_status")]=QVariant(status);
        param_rst_file[QString("file_pos")]=QVariant(pos);
        param_rst_file[QString("file_md5")]=QVariant(md5_value);
        break;
    }
    //如果客户端的请求类型为上传偏移量，解析客户端请求，并回传
    case FILE_UPLOAD_POS:{

        qint64 file_pos=param_qst_file[QString("file_pos")].toInt();
        this->m_file_totalBytes=param_qst_file[QString("file_size")].toInt();

        //根据客户端发送的文件偏移量设置文件的偏移量
        qint64 status=this->fileUploadPos(file_pos);
        if(status==OK)
        {
            this->m_fileupload_state=1;
        }

        param_rst_file[QString("reply_status")]=QVariant(status);
        break;
    }

    //如果客户端请求为下载，解析并回传信息
    case FILE_DOWNLOAD_HEADER:{
        this->paramInit();
        QString file_prjpath=param_qst_file[QString("file_prjpath")].toString();
        this->m_file_pos=param_qst_file[QString("file_pos")].toInt();
        QString file_md5_value=param_qst_file[QString("file_md5")].toString();

        qint64 status=this->fileDownloadHeader(file_prjpath,this->m_file_pos,file_md5_value,this->m_file_totalBytes);
        if(status==OK)
        {
            this->m_fileupload_state=0;
        }

        //回传客户端接收状态、文件偏移量、文件大小
        param_rst_file[QString("reply_status")]=QVariant(status);
        param_rst_file[QString("file_pos")]=QVariant(this->m_file_pos);
        param_rst_file[QString("file_size")]=QVariant(this->m_file_totalBytes);
        break;
    }

    //开始处理客户端的下载请求
    case FILE_DOWNLOAD_CONTENT:{
        reply=false;
        this->fileDownloadContent();
        break;
    }

    //处理完客户端的下载请求后向客户端回传信息
    case FILE_DOWNLOAD_TAIL:{
        qint64 status=OK;
        this->m_fileupload_state=0;
        param_rst_file[QString("reply_status")]=QVariant(status);
        break;
    }
    }
}

//初始化全局变量，包括文件总大小、状态、文件偏移量
void FileSocket::paramInit()
{
    this->m_file_totalBytes=0;
    this->m_status=OK;
    this->m_file_pos=0;
}

//处理客户端的上传头部信息
qint64 FileSocket::fileUploadHeader(QString filename, qint64 &pos, QString &md5_value)
{
    //获取服务器存储目录下的上传文件路径
    QString filepath=QString("%1/%2").arg(g_root_dir).arg(filename);
    this->m_local_file.setFileName(filepath);

    //如果上传文件不存在，返回OK
    if(this->m_local_file.exists()==false)
    {
        pos=0;
        return OK;
    }
    //上传的文件存在
    else
    {
        //获取目标文件已上传的大小
        pos=this->m_local_file.size();
        //如果已上传大小为0，相当于不存在
        if(pos==0)
        {
            return OK;
        }

        if(this->m_local_file.open(QIODevice::ReadOnly)==false)
        {
             pos=0;
             qDebug()<<"open file("<<this->m_local_file.fileName()<<") failed."<<endl;
             return FILE_OPEN_ERROR;
        }

        //获取已存在文件的MD5值，防止上传的文件已修改
        QCryptographicHash file_md5(QCryptographicHash::Md5);
        qint64 payloadSize=10*1024*1024;
        QByteArray file_data=this->m_local_file.read(payloadSize);
        while(!file_data.isEmpty())
        {
            file_md5.addData(file_data);
            file_data=this->m_local_file.read(payloadSize);
        }
        this->m_local_file.close();
        md5_value=QString(file_md5.result().toHex());

        return OK;
    }
}

//根据客户端发送的文件偏移量设置文件偏移量
qint64 FileSocket::fileUploadPos(qint64 file_pos)
{
    if(this->m_local_file.open(QIODevice::WriteOnly)==false)
    {
        qDebug()<<"open file("<<this->m_local_file.fileName()<<") failed."<<endl;
        return FILE_OPEN_ERROR;
    }

    if(this->m_local_file.seek(file_pos)==false)
    {
        qDebug()<<"seek file("<<this->m_local_file.fileName()<<") failed."<<endl;
        this->m_local_file.close();
        return FILE_SEEK_ERROR;
    }

    return OK;
}

//解析客户端的下载请求头
qint64 FileSocket::fileDownloadHeader(QString filename, qint64 &pos, QString &md5_value, qint64 &file_size)
{

    this->m_local_file.setFileName(QString("%1/%2").arg(g_root_dir).arg(filename));
    if(this->m_local_file.open(QIODevice::ReadOnly)==false)
    {
        qDebug()<<"file open error"<<endl;
        return FILE_OPEN_ERROR;
    }
    file_size=this->m_local_file.size();

    //如果客户端发送端的文件偏移量为0，说明客户端没有下载过文件，不需要进行断点续传操作，直接返回
    if(pos==0)
    {
        this->m_local_file.close();
        return OK;
    }


    //断点续传操作，需要验证客户端本地的文件MD5值
    QCryptographicHash file_md5(QCryptographicHash::Md5);
    qint64 payloadSize=10*1024*1024;
    qint64 file_pos=pos;
    qint64 readBytes=0;
    while(file_pos>0)
    {
        readBytes=qMin(file_pos,payloadSize);
        QByteArray file_data=this->m_local_file.read(readBytes);
        file_md5.addData(file_data);
        file_pos-=readBytes;
    }
    this->m_local_file.close();

    //验证文件MD5值
    if(QString(file_md5.result().toHex())!=md5_value)
    {
        pos=0;
    }
    //更新要传给客户端的剩余文件大小
    file_size-=pos;

    return OK;
}

//处理客户端的下载请求
qint64 FileSocket::fileDownloadContent()
{
    if(this->m_local_file.open(QIODevice::ReadOnly)==false)
    {
        qDebug()<<"file open error"<<endl;
        return FILE_OPEN_ERROR;
    }
    //设置文件偏移量
    this->m_local_file.seek(this->m_file_pos);

    //循环写入文件字节
    qint64 payloadSize=1*1024*1024;
    while(this->m_file_totalBytes!=0)
    {
        QByteArray readData=this->m_local_file.read(qMin(this->m_file_totalBytes,payloadSize));
        this->m_file_totalBytes-=this->m_socket.write(readData);
        this->m_socket.waitForBytesWritten();
    }

    return OK;
}
